using System;
using System.Data;
using System.Web;
using log4net.Appender;
using log4net.Core;
using SubSonic;

namespace CommonLibrary
{
	/// <summary>
	/// Allows the logging of log4net events to a database using SubSonic (http://www.subsonicproject.com/).
	/// </summary>
	public class SubSonicAppender : AppenderSkeleton
	{
		#region Constructor
		
		public SubSonicAppender()
		{
			Query q = new Query(TableName, ProviderName);
			SubSonicTable = q.Schema;

			if (SubSonicTable.Columns.Count == 0)
			{
				//The table doesn't exist.  Not sure why there isn't an exception around this, but anyway...

			}
			else
			{
				q.QueryType = QueryType.Insert;
				Command = new QueryCommand(DataService.GetSql(q), ProviderName);
				Command.ProviderName = ProviderName;
			}
		} 

		#endregion

		#region Private Fields

		private string _providerName;
		private string _tableName = "Log";
		private TableSchema.Table _subsonicTable;
		private QueryCommand _command;
		private string _dateColumnName = "Date";
		private string _threadColumnName = "Thread";
		private string _levelColumnName = "Level";
		private string _loggerColumnName = "Logger";
		private string _messageColumnName = "Message";
		private string _exceptionColumnName = "Exception";
		private string _urlColumnName = "Url";
		private string _ipColumnName = "Ip";
		private bool? _logIpAddress;
		private bool? _logUrl;

		#endregion

		#region Protected Properties

		/// <summary>
		/// The SubSonic command to execute the insert
		/// </summary>
		protected QueryCommand Command
		{
			get { return _command; }
			set { _command = value; }
		}

		/// <summary>
		/// The table that stores the data
		/// </summary>
		protected TableSchema.Table SubSonicTable
		{
			get { return _subsonicTable; }
			set { _subsonicTable = value; }
		}

		#endregion

		#region Public Properties

		/// <summary>
		/// Whether to log the IP address.  Will be false if there is no current HTTPContext or the underlying table
		/// doesn't have an IP column
		/// </summary>
		public bool LogIpAddress
		{
			get
			{
				if (!_logIpAddress.HasValue)
				{
					_logIpAddress = (HttpContext.Current != null) && (SubSonicTable.Columns.Contains(IpColumnName));
				}
				return _logIpAddress.Value;
			}
			set { _logIpAddress = value; }
		}

		/// <summary>
		/// Whether to log the requested URL.  Will be false if there is no current HTTPContext or the underlying table
		/// doesn't have a URL column
		/// </summary>
		public bool LogUrl
		{
			get
			{
				if (!_logUrl.HasValue)
				{
					_logUrl = (HttpContext.Current != null) && (SubSonicTable.Columns.Contains(UrlColumnName));
				}
				return _logUrl.Value;
			}
			set { _logUrl = value; }
		}

		#region Column Names

		/// <summary>
		/// The name of the column to store the requested url in. Default is 'Url'
		/// </summary>
		public string UrlColumnName
		{
			get { return _urlColumnName; }
			set { _urlColumnName = value; }
		}

		/// <summary>
		/// The name of the column to store the host IP in. Default is 'Ip'
		/// </summary>
		public string IpColumnName
		{
			get { return _ipColumnName; }
			set { _ipColumnName = value; }
		}

		/// <summary>
		/// The name of the column to store the exception in. Default is 'Exception'.  WIll be null if no
		/// exception passed in
		/// </summary>
		public string ExceptionColumnName
		{
			get { return _exceptionColumnName; }
			set { _exceptionColumnName = value; }
		}

		/// <summary>
		/// The name of the column to store the message in. Default is 'Message'
		/// </summary>
		public string MessageColumnName
		{
			get { return _messageColumnName; }
			set { _messageColumnName = value; }
		}

		/// <summary>
		/// The name of the column to store the logger info in. Default is 'Logger'
		/// </summary>
		public string LoggerColumnName
		{
			get { return _loggerColumnName; }
			set { _loggerColumnName = value; }
		}

		/// <summary>
		/// The name of the column to store the level in. Default is 'Level'
		/// </summary>
		public string LevelColumnName
		{
			get { return _levelColumnName; }
			set { _levelColumnName = value; }
		}

		/// <summary>
		/// The name of the column to store the thread info in. Default is 'Thread'
		/// </summary>
		public string ThreadColumnName
		{
			get { return _threadColumnName; }
			set { _threadColumnName = value; }
		}

		/// <summary>
		/// The name of the column to store the datetime in. Default is 'Date'
		/// </summary>
		public string DateColumnName
		{
			get { return _dateColumnName; }
			set { _dateColumnName = value; }
		} 

		#endregion

		/// <summary>
		/// The name of the table to log values to. Default is "Log"
		/// </summary>
		public string TableName
		{
			get { return _tableName; }
			set { _tableName = value; }
		}

		/// <summary>
		/// The name of the SubSonic provider to use.  Will default to the default provider
		/// </summary>
		public string ProviderName
		{
			get { return _providerName; }
			set
			{
				if (string.IsNullOrEmpty(_providerName))
				{
					_providerName = DataService.Provider.Name;
				}
				_providerName = value;
			}
		}

		#endregion

		#region Methods
		
		/// <summary>
		/// Logs the event to the database
		/// </summary>
		/// <param name="loggingEvent">The event to append</param>
		protected override void Append(LoggingEvent loggingEvent)
		{
			Command.Parameters =
				GenerateParameters(loggingEvent.TimeStamp, loggingEvent.ThreadName, loggingEvent.Level.Name, loggingEvent.LoggerName,
								   loggingEvent.MessageObject.ToString(), loggingEvent.ExceptionObject);

			DataService.ExecuteQuery(Command);
		}

		/// <summary>
		/// Generate the parameter collection to pass into the command to execute.
		/// </summary>
		/// <param name="timestamp"></param>
		/// <param name="thread"></param>
		/// <param name="level"></param>
		/// <param name="loggerType"></param>
		/// <param name="message"></param>
		/// <param name="exception"></param>
		/// <returns></returns>
		private QueryParameterCollection GenerateParameters(DateTime timestamp, string thread, string level, string loggerType, string message, object exception)
		{
			QueryParameterCollection col = new QueryParameterCollection();
			col.Add(DateColumnName, timestamp, DbType.DateTime);
			col.Add(ThreadColumnName, thread, DbType.String);
			col.Add(LevelColumnName, level, DbType.String);
			col.Add(LoggerColumnName, loggerType, DbType.String);
			col.Add(MessageColumnName, message, DbType.String);
			if (LogIpAddress)
			{
				col.Add(IpColumnName, HttpContext.Current.Request.UserHostAddress, DbType.String);
			}
			if (LogUrl)
			{
				col.Add(UrlColumnName, HttpContext.Current.Request.Url.ToString(), DbType.String);
			}
			col.Add(ExceptionColumnName, exception ?? DBNull.Value, DbType.String);
			return col;
		} 

		#endregion
	}
}